package git.bankview.sl.servlet;

import java.io.*;
import java.util.*;
import java.text.*;

import javax.servlet.*;

import org.apache.log4j.Logger;

public class MultipartPostRequestAdapter
{
	private static Logger log = Logger.getLogger(MultipartPostRequestAdapter.class);
    
	private static short m_nSerialNo = 0x00;
	private static SimpleDateFormat m_oPrefixFormat = new SimpleDateFormat("yyyyMMdd.HHmmss.SSS.");
	
	private HashMap m_oParaMap = new HashMap();
	private HashMap m_oFileMap = new HashMap();
	
	private byte[] m_vSp = null;
	private int m_nSpLen = -1;
	
	private String m_oCharSet = null;
	
	public MultipartPostRequestAdapter(ServletRequest request)
		throws ServletException, IOException
	{
		init(request, 0, 1024, null, null);
	}
	
	public MultipartPostRequestAdapter(ServletRequest request, long nMaxUploadBytes)
		throws ServletException, IOException
	{
		init(request, nMaxUploadBytes, 1024, null, null);
	}
	
	public MultipartPostRequestAdapter(ServletRequest request, long nMaxUploadBytes, int nBuffSize)
		throws ServletException, IOException
	{
		init(request, nMaxUploadBytes, nBuffSize, null, null);
	}
	
	public MultipartPostRequestAdapter(ServletRequest request, long nMaxUploadBytes, int nBuffSize, String tmpDir)
		throws ServletException, IOException
	{
		init(request, nMaxUploadBytes, nBuffSize, tmpDir, null);
	}

	public MultipartPostRequestAdapter(ServletRequest request, ITransportProgressListener listener)
		throws ServletException, IOException
	{
		init(request, 0, 1024, null, listener);
	}
	
	public MultipartPostRequestAdapter(ServletRequest request, long nMaxUploadBytes, ITransportProgressListener listener)
		throws ServletException, IOException
	{
		init(request, nMaxUploadBytes, 1024, null, listener);
	}
	
	public MultipartPostRequestAdapter(ServletRequest request, long nMaxUploadBytes, int nBuffSize, ITransportProgressListener listener)
		throws ServletException, IOException
	{
		init(request, nMaxUploadBytes, nBuffSize, null, listener);
	}
	
	public MultipartPostRequestAdapter(ServletRequest request, long nMaxUploadBytes, int nBuffSize, String tmpDir, ITransportProgressListener listener)
		throws ServletException, IOException
	{
		init(request, nMaxUploadBytes, nBuffSize, tmpDir, listener);
	}
	
	public String getParameter(String param)
	{
		String[] v = getParameterValues(param);
		return null == v ? null : v[0];
	}
	
	public String[] getParameterValues(String param)
	{
		Vector v = (Vector)m_oParaMap.get(param);
		return null == v ? null : (String[])v.toArray(new String[0]);
	}
	
	public File getUploadFile(String param)
	{
		File[] v = getUploadFiles(param);
		return null == v ? null : v[0];
	}
	
	public File[] getUploadFiles(String param)
	{
		Vector v = (Vector)m_oFileMap.get(param);
		return null == v ? null : (File[])v.toArray(new File[0]);
	}
	
	private void init(ServletRequest request, long nMaxUploadBytes, int nBuffSize, String tmpDir, ITransportProgressListener listener)
		throws ServletException, IOException
	{
		m_oCharSet = request.getCharacterEncoding();
		
		ServletInputStream is = request.getInputStream();
	
		m_vSp = new byte[nBuffSize];
		byte[] vLineBuff = new byte[nBuffSize];
		Properties oFieldProps = null;
		StringBuffer oFieldValue = null;
		String oFileName = null;
		OutputStream fos = null;
		
		int nStep = 1;
		long nContentLength = request.getContentLength();
		long nTotalBytes = 0;
		byte[] vLastBytes = null;
		
		try
		{
			if (null != listener)
				if (!listener.onProgress(nTotalBytes, nContentLength))
					throw new IOException("canceled by user");
			
			while(0 < nStep)
			{
				int nLen = is.readLine(vLineBuff, 0, vLineBuff.length);
				
				if (-1 == nLen)
				{
					log.debug("readLine() returns -1!!!");
					break;
				}
					
				switch(nStep)
				{
					case 1:
					{
						if (getSpLine(vLineBuff, nLen))
						{
							nStep = 2;
						}
						break;
					}
					case 2:
					{
						oFieldProps = getFieldProps(vLineBuff, nLen);
						nStep = 3;
						
						break;
					}
					case 3:
					{
						if (0x0d == vLineBuff[0] && 2 >= nLen)
						{
							log.debug("Field Properties: " + oFieldProps);
							
							oFileName = oFieldProps.getProperty("filename");
							if (null == oFileName)
							{
								vLastBytes = null;
								nStep = 4;
							}
							else
							{
								File oLocalFile = newTempFile(tmpDir);
								fos = new BufferedOutputStream(new FileOutputStream(oLocalFile));
								addParameter(m_oParaMap, oFieldProps.getProperty("name"), oFileName);
								addParameter(m_oFileMap, oFieldProps.getProperty("name"), oLocalFile);
								vLastBytes = null;
								nStep = 5;
							}
							
							oFieldValue = new StringBuffer();
						}
						else
						{
							Properties tmp = getFieldProps(vLineBuff, nLen);
							oFieldProps.putAll(tmp);
						}
						break;
					}
					case 4:
					{
						switch(getLineType(vLineBuff, nLen))
						{
							case -1:
							{
								log.debug("getLineType() returns -1; step: " + nStep);
								addParameter(m_oParaMap, oFieldProps.getProperty("name"), oFieldValue.toString());
								nStep = 0;
								break;
							}
							case 0:
							{
								addParameter(m_oParaMap, oFieldProps.getProperty("name"), oFieldValue.toString());
								nStep = 2;
								break;
							}
							case 1:
							{
								if (null != vLastBytes && 0 < vLastBytes.length)
								{
									appendFieldBytes(oFieldValue, vLastBytes, vLastBytes.length);
								}

								int nNeed = (2 <= nLen ? 2 : nLen);

								if (null == vLastBytes || nNeed != vLastBytes.length)
								{
									vLastBytes = new byte[nNeed];
								}

								for (int i = 0; i < nNeed; ++i)
								{
									vLastBytes[i] = vLineBuff[nLen - nNeed + i];
								}
								
								appendFieldBytes(oFieldValue, vLineBuff, nLen - nNeed);
								break;
							}
						}
						break;
					}
					case 5:
					{
						switch(getLineType(vLineBuff, nLen))
						{
							case -1:
							{
								log.debug("getLineType() returns -1; step: " + nStep);
								fos.close();
								nStep = 0;
								break;
							}
							case 0:
							{
								fos.close();
								nStep = 2;
								break;
							}
							case 1:
							{
								if (null != vLastBytes && 0 < vLastBytes.length)
								{
									fos.write(vLastBytes, 0, vLastBytes.length);
								}

								int nNeed = (2 <= nLen ? 2 : nLen);
														
								if (null == vLastBytes || nNeed != vLastBytes.length)
								{
									vLastBytes = new byte[nNeed];
								}

								for (int i = 0; i < nNeed; ++i)
								{
									vLastBytes[i] = vLineBuff[nLen - nNeed + i];
								}
								
								fos.write(vLineBuff, 0, nLen - nNeed);
								break;
							}
						}
						break;
					}
				} // end switch(nStep)
				
				nTotalBytes += nLen;
				if (0 < nMaxUploadBytes)
				{
					if (nMaxUploadBytes <= (nTotalBytes))
					{
						nStep = -1;
					}
				}
				
				if (null != listener)
					if (!listener.onProgress(nTotalBytes, nContentLength))
						throw new IOException("canceled by user");
			} // end while(0 < nStep)
		}
		catch(IOException e)
		{
			log.warn("parse HTTP content IO error: ", e);
			throw e;
		}
		catch(Exception e)
		{
			log.warn("parse HTTP content error: ", e);
			throw new ServletException("parse HTTP content error: ", e);
		}
		finally
		{
			if (5 == nStep)
			{
				fos.close();
			}
		}
	}
	
	private void appendFieldBytes(StringBuffer oFieldValue, byte[] vBuff, int nLen)
		throws UnsupportedEncodingException
	{
		if (null != m_oCharSet)
		{
			oFieldValue.append(new String(vBuff, 0, nLen, m_oCharSet));
		}
		else
		{
			oFieldValue.append(new String(vBuff, 0, nLen));
		}
	}
	
	private int getLineType(byte[] vLineBuff, int nLen)
	{
		if (nLen == m_nSpLen)
		{
			if (compareData(vLineBuff, m_vSp, m_nSpLen))
			{
				return 0; // sp line
			}
		}
		else if (nLen > m_nSpLen)
		{
			if (compareData(vLineBuff, m_vSp, m_nSpLen - 2))
			{
				return -1; // end line
			}
		}
		
		return 1; // data line
	}
	
	private boolean compareData(byte[] vLine1, byte[] vLine2, int nLen)
	{
		for (int i = 0; i < nLen; ++i)
		{
			if (vLine1[i] != vLine2[i])
				return false;
		}
		
		return true;
	}
	
	private boolean getSpLine(byte[] vLineBuff, int nLen)
	{
		int i = 0;
		for (; i < nLen; ++i)
		{
			m_vSp[i] = vLineBuff[i];
			
			if ('-' != vLineBuff[i])
			{
				if (0 == i)
					return false;
				
				break;
			}
		}
		
		for (; i < nLen; ++i)
		{
			if ('-' == vLineBuff[i])
			{
				return false;
			}
			
			m_vSp[i] = vLineBuff[i];
		}
		
		if (0 < i)
		{
			m_nSpLen = i;
			return true;
		}
		
		return false;
	}
	
	private Properties getFieldProps(byte[] vLineBuff, int nLen)
	{
		LexParser oParser = new LexParser(vLineBuff, nLen);
		Properties oProps = new Properties();
		
		int nStep = 1;
		String oName = null;
		for (String oWord = oParser.nextWord(); null != oWord; oWord = oParser.nextWord())
		{
			switch(nStep)
			{
				case 1:
				{
					oName = oWord;
					if (!oWord.equals(";"))
					{
						nStep = 2;
					}
					break;
				}
				case 2:
				{
					if (oWord.equals(":"))
					{
						nStep = 3;
					}
					else if (oWord.equals("="))
					{
						nStep = 4;
					}
					else
					{
						nStep = 1;
					}
					break;
				}
				case 3:
				{
					String oValue = oWord;
					if (!oValue.equals(";"))
					{
						oProps.setProperty(oName.toLowerCase(), oValue);
					}
					nStep = 1;
					break;
				}
				case 4:
				{
					String oValue = oWord;
					if (!oValue.equals(";"))
					{
						oProps.setProperty(oName.toLowerCase(), oValue.substring(1, oValue.length() - 1));
					}
					nStep = 1;
					break;
				}
			}
		}
		
		return oProps;
	}
	
	private static void addParameter(HashMap map, String name, Object value)
	{         
		Vector oParaList = (Vector)map.get(name);
		
		if (null == oParaList)
		{
			oParaList = new Vector();
			map.put(name, oParaList);
		}
		
		oParaList.addElement(value);
	}         
              
	private File newTempFile(String tmpDir)
		throws IOException
	{
		if (null == tmpDir)
		{
			return File.createTempFile("Upload at " + m_oPrefixFormat.format(new Date()), ".tmp");
		}
		else
		{
			return File.createTempFile("Upload at " + m_oPrefixFormat.format(new Date()), ".tmp", new File(tmpDir));
		}
	}
	
	private void debug(byte[] v, int nLen, String prefix)
	{
		log.debug(prefix + "; " + new String(v, 0, nLen));
	}
}

class LexParser
{
	private static Logger log = Logger.getLogger(LexParser.class);
	
	byte[] m_vLineBuff = null;
	int m_nLen = 0;
	int m_nIndex = 0;
	
	LexParser(byte[] vLineBuff, int nLen)
	{
		m_vLineBuff = vLineBuff;
		m_nLen = nLen;
	}
	
	String nextWord()
	{
		byte[] vWord = new byte[256];
		int nLen = 0;
		int nStep = 1;
		
		for (; m_nIndex < m_nLen; )
		{
			byte ch = m_vLineBuff[m_nIndex++];
			
			switch(nStep)
			{
				case 1:
				{
					switch(ch)
					{
						case ':':
							return ":";
						case ';':
							return ";";
						case '=':
							return "=";
						case ' ':
							break;
							
						case '"':
							vWord[nLen++] = ch; nStep = 3; break;
						default:
							vWord[nLen++] = ch; nStep = 2; break;
					}
					break;
				}
				case 2:
				{
					switch(ch)
					{
						case ':': case ';': case '=':
							--m_nIndex;
							return (new String(vWord, 0, nLen)).trim();
					}
					vWord[nLen++] = ch; 
					break;
				}
				case 3:
				{
					vWord[nLen++] = ch;
					switch(ch)
					{
						case '"':
							return new String(vWord, 0, nLen);
					}
					break;
				}
			}
		}
		
		if (0 < nLen)
		{
			return (new String(vWord, 0, nLen)).trim();
		}
		
		return null;
	}
}

